package com.swak.vertx.security;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.List;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileUrlResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.UrlResource;
import org.springframework.util.ResourceUtils;

import com.swak.exception.BaseRuntimeException;
import com.swak.security.JWTAuthOptions;
import com.swak.security.Subject;
import com.swak.security.jwt.JWK;
import com.swak.security.jwt.JWT;
import com.swak.security.jwt.JWTOptions;
import com.swak.security.jwt.JWTPayload;
import com.swak.security.options.KeyStoreOptions;
import com.swak.security.options.PubSecKeyOptions;
import com.swak.security.options.SecretOptions;
import com.swak.utils.IOUtils;
import com.swak.utils.StringUtils;

import lombok.Getter;
import lombok.Setter;
import lombok.experimental.Accessors;

/**
 * jwt 的授权实现
 *
 * @author lifeng
 */
public class SecurityService {

	private static final Logger logger = LoggerFactory.getLogger(Subject.class);

	// private final JWT jwt;
	private final JWTOptions options;
	private final JWTBuilder builder;

	// private final JWT jwt;
	private final ThreadLocal<JWT> holder = new ThreadLocal<JWT>() {
		@Override
		protected JWT initialValue() {
			return builder.build();
		}
	};

	/**
	 * 保存JWT的创建参数
	 */
	@Getter
	@Setter
	@Accessors(chain = true)
	static class JWTBuilder {
		JWTAuthOptions config;
		String keyStorePath;
		String keyStorePass;
		String keyStoreAlgorithm;

		JWT build() {

			if (logger.isDebugEnabled()) {
				logger.debug("BuildJWT in Thread:" + Thread.currentThread().getName());
			}

			if (config != null) {
				return SecurityService.build(config);
			}
			return SecurityService.build(keyStorePath, keyStorePass, keyStoreAlgorithm);
		}
	}

	private static synchronized KeyStore loadKeyStore(String type, String path, String pass)
			throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
		InputStream ksPath;
		try {
			// 加载默认位置的 keystore
			if (StringUtils.isBlank(path)) {

				// classes 目录下
				ksPath = SecurityService.class.getClassLoader().getResourceAsStream("keystore.jceks");

				// 当前目录下
				if (ksPath == null) {
					ksPath = SecurityService.class.getResourceAsStream("keystore.jceks");
				}
			}
			// 自定义的的keystore
			else {
				ksPath = getResource(path).getInputStream();
			}
		} catch (Exception e) {
			throw new BaseRuntimeException(e);
		}

		// 支持值这个类型
		KeyStore keyStore = KeyStore.getInstance(type);

		// 加载 keyStore
		keyStore.load(ksPath, pass.toCharArray());

		// 关闭资源
		IOUtils.closeQuietly(ksPath);

		// 返回加载好的 keyStore
		return keyStore;
	}

	private static Resource getResource(String path) {
		if (path.startsWith(ResourceLoader.CLASSPATH_URL_PREFIX)) {
			return new ClassPathResource(path.substring(ResourceLoader.CLASSPATH_URL_PREFIX.length()));
		} else {
			try {
				URL url = new URL(path);
				return (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
			} catch (Exception e) {
				return new ClassPathResource(path);
			}
		}
	}

	private static JWT build(JWTAuthOptions config) {
		final KeyStoreOptions keyStore = config.getKeyStore();
		JWT jwt = null;
		try {
			if (keyStore != null) {
				KeyStore ks = loadKeyStore(keyStore.getType(), keyStore.getPath(), keyStore.getPassword());
				jwt = new JWT(ks, keyStore.getPassword().toCharArray());
			} else {
				jwt = new JWT();

				// the better way: use JWK
				final List<PubSecKeyOptions> keys = config.getPubSecKeys();

				if (keys != null) {
					for (PubSecKeyOptions pubSecKey : config.getPubSecKeys()) {
						if (pubSecKey.isSymmetric()) {
							jwt.addJWK(new JWK(pubSecKey.getAlgorithm(), pubSecKey.getPublicKey()));
						} else {
							jwt.addJWK(new JWK(pubSecKey.getAlgorithm(), pubSecKey.isCertificate(),
									pubSecKey.getPublicKey(), pubSecKey.getSecretKey()));
						}
					}
				}

				// the better way: use JWK
				final List<SecretOptions> secrets = config.getSecrets();

				if (secrets != null) {
					for (SecretOptions secret : secrets) {
						jwt.addSecret(secret.getType(), secret.getSecret());
					}
				}

				final List<JWK> jwks = config.getJwks();

				if (jwks != null) {
					for (JWK jwk : jwks) {
						jwt.addJWK(jwk);
					}
				}
			}
		} catch (Exception e) {
			throw new BaseRuntimeException(e);
		}
		return jwt;
	}

	private static JWT build(String keyStorePath, String keyStorePass, String keyStoreAlgorithm) {
		try {
			KeyStore keyStore = loadKeyStore("jceks", keyStorePath, keyStorePass);
			return new JWT(keyStore, keyStorePass.toCharArray());
		} catch (Exception e) {
			throw new BaseRuntimeException(e);
		}
	}

	/**
	 * 通过配置化的方式来存储密码
	 *
	 * @param config 配置
	 */
	public SecurityService(JWTAuthOptions config) {
		this.options = config.getJWTOptions();
		this.builder = new JWTBuilder().setConfig(config);
	}

	/**
	 * 默认的通过： keyStore 的方式来存储密码
	 *
	 * @param keyStorePath      路径
	 * @param keyStorePass      密码
	 * @param keyStoreAlgorithm 算法
	 * @param expiresInSeconds  有效期
	 */
	public SecurityService(String keyStorePath, String keyStorePass, String keyStoreAlgorithm,
			Integer expiresInSeconds) {
		this.options = new JWTOptions().setAlgorithm(keyStoreAlgorithm);
		if (expiresInSeconds != null) {
			this.options.setExpiresInSeconds(expiresInSeconds);
			this.options.setIgnoreExpiration(false);
		}
		this.builder = new JWTBuilder().setKeyStorePath(keyStorePath).setKeyStorePass(keyStorePass)
				.setKeyStoreAlgorithm(keyStoreAlgorithm);
	}

	/**
	 * 签名，生成 token: 登陆的时候使用
	 *
	 * @param payload 数据
	 * @return 生成 token
	 */
	public String generateToken(JWTPayload payload) {

		if (logger.isDebugEnabled()) {
			logger.debug("GenerateToken");
		}

		JWT jwt = holder.get();
		return jwt.sign(payload, options);
	}

	/**
	 * 验证 token
	 *
	 * @param token 生成 token
	 * @return JWTPayload
	 */
	public JWTPayload verifyToken(String token) {

		if (logger.isDebugEnabled()) {
			logger.debug("VerifyToken");
		}

		/*
		 * 必须有数据
		 */
		if (StringUtils.isBlank(token)) {
			return null;
		}

		/*
		 * 当前线程的jwt
		 */
		JWT jwt = holder.get();

		/*
		 * 解密token
		 */
		JWTPayload payload = jwt.decode(token);

		/*
		 * 验证失败会抛出异常
		 */
		if (!options.isIgnoreExpiration()) {
			jwt.isExpired(payload, options);
		}

		/*
		 * 能走到这里必然成功
		 */
		return payload;
	}

}